
/*#
# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code is not supported by Google
#
*/
/**
	Sample code to Parse and render GCS Category Response.
	Usage:
	javac -cp . ParseGCSCategories.java
	java -cp . -Dfile.encoding=UTF-8 ParseGCSCategories "full_gcs_query"
	
	* use quotes around full_gcs_query
	* replace YOUR_API_KEY with your shopping api key
	
	To use against your own taxonomy, you must first upload and configure
	GCS Browse/Navigation API:
	https://developers.google.com/commerce-search/docs/browse

	
	Sample usage with default taxonomy (&taxonomy=private:).  To see the browseAPI
	on an example taxonomy, replace YOUR_API_KEY with your valid shopping api below
	
	gives full taxonomy
      https://www.googleapis.com/shopping/search/v1/cx:005865242780510660151:o9vudkrzvj8/
      products?alt=atom&country=us&categories.enabled=true&key=YOUR_API_KEY&taxonomy=private:&
      facets.enabled=true&facets.useGcsConfig=true&facets.include=category:50&
      facets.discover=20:20&q=&categories.enabled=true&categories.useGcsConfig=false&
      categories.include=roots(children:5)

    gives breadcrumbs from node 69180
      https://www.googleapis.com/shopping/search/v1/cx:005865242780510660151:o9vudkrzvj8/
      products?alt=atom&country=us&categories.enabled=true&key=YOUR_API_KEY&taxonomy=private:&
      facets.enabled=true&facets.useGcsConfig=true&facets.discover=20:20&q=&
      categories.include=node:69180(parents)&restrictBy=category=69180

    gives breadcrumbs for all matching facet-categories that match q=san+francisco
      https://www.googleapis.com/shopping/search/v1/cx:005865242780510660151:o9vudkrzvj8/
      products?alt=atom&country=us&categories.enabled=true&key=YOUR_API_KEY&taxonomy=private:&
      facets.enabled=true&facets.useGcsConfig=true&facets.discover=20:20&q=san+francisco&
      categories.enabled=true&categories.useGcsConfig=true&categories.include=facets(parents)
      
    
		categories.include  
		    roots(children:5)     --> get taxonomy 5 levels down from root   
			node:3634(children:5)    --> get taxonomy 5 levels down from node id=3634
		    node:69180(parents)       --> get taxonomy levels up from node id=69180
		taxonomy
		    private:common		  --> taxonomy to parse in the form 
		    							private:<group_name>
		    							(the examples above uses the default group:
		    							&taxonomy=private:&
		    							if you defined your taxonomy group as
		    							private:common, specify that.		    	
		    							      
      
	Note:  to use it with your own GCS instance, first create a Facet Include rule to return 
	the 'category' attribute:
	
	<Ruleset type="FACET" match_all="false">
      <Rule name="category">
        <Action type="FACET">
          <IncludeFacet max_buckets="20" name="category" type="TEXT"/>
        </Action>
      </Rule>
    </Ruleset>
    
    https://developers.google.com/commerce-search/docs/gcsconfiguration#facet
    
						
*/

import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class ParseGCSCategories extends JPanel {

    private String base_shopping_query;
    
    JTree tree;
    JTextField jtf;    
    
	public static void main(String[] args) {
		if (args.length<1)
		{
			System.out.println("Usage:  ParseGCSCategories shopping_query");
			System.exit(0);
		}
	    JFrame frame = new JFrame("Taxonomy Tree");
	    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	    frame.add(new ParseGCSCategories(args[0]));
	    frame.setSize(500, 500);
	    frame.setVisible(true);
	}
	
	
	public  ParseGCSCategories(String base_shopping_query)
	{
		this.base_shopping_query = base_shopping_query;	
		System.out.println(base_shopping_query);
		try
		{  
			URL url = new URL(base_shopping_query);		    
		    HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
		    InputStream inputStream = httpConnection.getInputStream();
		 
		    //use a SAX parser since it'd be faster for large data sets		    
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser saxParser = factory.newSAXParser();	

			// array to hold all the unordered categories
		    final Hashtable<String, Category> ht_cat = new Hashtable<String, Category>();

			DefaultHandler handler = new DefaultHandler() {				 
					boolean is_category = false;
		
					Category c = null;
					ArrayList<String> parents = new ArrayList<String>();
				 
					public void startElement(String uri, String localName,String qName, 
				               Attributes attributes) throws SAXException {
						if (qName.equalsIgnoreCase("s:category")) {
							is_category = true;	
							String id = attributes.getValue("id");
							String shortName = attributes.getValue("shortName");
							String url = attributes.getValue("url");
							parents = new ArrayList<String>();
							c = new Category(shortName,id,url,new String[0],true);
						}
		
						if (qName.equalsIgnoreCase("s:parent")) {
							parents.add(attributes.getValue("id"));
				    		String[] sarr = new String[parents.size()];
				    		sarr = parents.toArray(sarr);
				    		if (parents.size() > 0)
				    			c.is_root = false;
							c.parents = sarr;
						}				 
					}				 
					public void endElement(String uri, String localName,
						String qName) throws SAXException {
						if (qName.equalsIgnoreCase("s:category")) {
							if (c.name != null)
								ht_cat.put(c.id,c);
						}
					}
				 
					public void characters(char ch[], int start, int length) throws SAXException {
					}		 
		   };
			                  
	     Date starttime = new Date();
	     
         saxParser.parse(inputStream, handler);               
         
         Date endtime = new Date();
     	 long    time_taken = endtime.getTime() - starttime.getTime();
     	 
             //Enumeration keys = ht_cat.keys();
             //while (keys.hasMoreElements()) {
             //    Category c = (Category)ht_cat.get(keys.nextElement());
             //    System.out.println( "Found Node: [" + c.name+ "]" + " ID: " + c.id + " parents: " + Arrays.asList(c.parents)); 
             //}

     	System.out.println("Time taken to parse taxonomy: " + time_taken + "ms");
     	
     	// create a default 'root' object since users can define multiple roots
        Category default_root = new Category("-", "ShoppingAPI",this.base_shopping_query,null, true);
        
	    DefaultMutableTreeNode root_node = new DefaultMutableTreeNode(default_root);
	    
	    // first find the relative roots in the taxonomy
	    ArrayList<DefaultMutableTreeNode> roots = this.getRoots(ht_cat);
	    for (DefaultMutableTreeNode curr : roots) 
	    	root_node.add(getChildren(curr, ht_cat));	 

	    setLayout(new BorderLayout());
	    tree = new JTree(root_node);	    
	    int v = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
	    int h = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
	    JScrollPane jsp = new JScrollPane(tree, v, h);

	    add(jsp, BorderLayout.CENTER);

	    jtf = new JTextField("", 20);
	    add(jtf, BorderLayout.SOUTH);

	    // to display the url when someone click expand
	    tree.addMouseListener(new MouseAdapter() {
	      public void mouseClicked(MouseEvent me) {
	        doMouseClicked(me);
	      }
	    });
	    
		}
		catch (java.io.IOException ioex)
		{
			System.out.println("unable to recall taxonomy: " + this.base_shopping_query);
			System.out.println("Check the API KEY, cx and taxonomy version");
			System.exit(0);
		}
		catch (Exception ex)
		{
			System.out.println("Error " + ex);
			System.exit(0);
		}
	}
	
	private  void doMouseClicked(MouseEvent me) {			
		    TreePath tp = tree.getPathForLocation(me.getX(), me.getY());
		      tree.setSelectionPath(tp);
		      if (tp != null)
		      {    	  
			    DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
			    Category c = (Category) node.getUserObject();			      
			    if (tp != null)
			      jtf.setText(c.url);
			    else
			      jtf.setText("");
		      }
		  }	

	// depth first recursion to get children of a node
	private DefaultMutableTreeNode getChildren(DefaultMutableTreeNode curr,Hashtable ht_cat)
	{
		Category cat = (Category)curr.getUserObject();
		DefaultMutableTreeNode ret = new DefaultMutableTreeNode(cat);
		
        Enumeration keys = ht_cat.keys();
        // for each element, see if the current node is a child of what was passed in
        // TODO: improve this (maybe cull ht_cat if an node is already used?)
        while (keys.hasMoreElements()) {
            Category c = (Category)ht_cat.get(keys.nextElement());
            if (Arrays.asList(c.parents).contains(cat.id)){
            	DefaultMutableTreeNode n = new DefaultMutableTreeNode(c);
            	ret.add(getChildren(n, ht_cat));
            }
        }  				
		return ret;
	}
	private ArrayList<DefaultMutableTreeNode> getRoots(Hashtable ht_cat) {
		ArrayList<DefaultMutableTreeNode> roots = new ArrayList<DefaultMutableTreeNode>();
        Enumeration keys = ht_cat.keys();
        while (keys.hasMoreElements()) {
            Category c = (Category)ht_cat.get(keys.nextElement());
            if (c.is_root)
            	roots.add(new DefaultMutableTreeNode(c));
        }        
        return roots;
	}

}

class Category
{
    public String name;
    public String id;
    public String url;
    String[] parents;
    boolean is_root = false;

    public Category(String name, String id, String url, String[] parents, boolean is_root) 
    {
        this.name = name;
        this.id = id;
        this.url = url;
        this.parents = parents;
        this.is_root = is_root;
    }
       
	  public String toString() {
		    return "[" + id + "] " + name;
		  }
}

